package qmlt.learning.neuralnetwork.control;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

import qmlt.dataset.DataSet;
import qmlt.evaluating.Evaluator;
import qmlt.evaluating.RegressionSquareErrorEvaluator;
import qmlt.learning.neuralnetwork.NeuralNetwork;
import qmlt.learning.neuralnetwork.node.OutputNode;
import qmlt.learning.neuralnetwork.node.OutputNodeC;

@SuppressWarnings("unchecked")
public class BasicNeuralNetworkController implements NeuralNetworkController
{
	// model parameters
	public Class<? extends OutputNode>	outputNodeClass	= OutputNodeC.class;

	public int													nInputs;

	public int[]												nHiddenLayerNodes;

	public float												learningRate		= 1;

	public float												momentum				= 0;

	public float												decay						= 0;

	private final Map<String, Float>		outputConversionMap;

	// controlling parameters
	public final DataSet								validateSet;

	public int													maxIteration		= 10000;

	public boolean											verbose					= false;

	public final PrintStream						out							= System.out;

	// internals
	public final Evaluator							evaluator				= new RegressionSquareErrorEvaluator<NeuralNetwork>();

	protected int												bestIter				= -1;

	protected float											bestScore				= Float.NEGATIVE_INFINITY;

	public BasicNeuralNetworkController(Map<String, Float> outputConversionMap,
			DataSet validationSet, int nInputs, int... nHiddenLayerNodes)
	{
		this.outputConversionMap = outputConversionMap;
		this.validateSet = validationSet;
		this.nInputs = nInputs;
		this.nHiddenLayerNodes = nHiddenLayerNodes;
	}

	public BasicNeuralNetworkController(String posString, Float posValue, String negString,
			Float negValue, DataSet validationSet, int nInputs, int... nHiddenLayerNodes)
	{
		outputConversionMap = new HashMap<String, Float>();
		outputConversionMap.put(posString, posValue);
		outputConversionMap.put(negString, negValue);
		this.validateSet = validationSet;
		this.nInputs = nInputs;
		this.nHiddenLayerNodes = nHiddenLayerNodes;
	}

	public BasicNeuralNetworkController(DataSet validationSet, int nInputs,
			int... nHiddenLayerNodes)
	{
		this.outputConversionMap = null;
		this.validateSet = validationSet;
		this.nInputs = nInputs;
		this.nHiddenLayerNodes = nHiddenLayerNodes;
	}

	@Override
	public Class<? extends OutputNode> getOutputNodeClass()
	{
		return outputNodeClass;
	}

	@Override
	public int getnInputs()
	{
		return nInputs;
	}

	@Override
	public int[] getnHiddenLayerNodes()
	{
		return nHiddenLayerNodes;
	}

	@Override
	public float getLearningRate(int iteration)
	{
		return learningRate;
	}

	@Override
	public float getDecay()
	{
		return decay;
	}

	@Override
	public float getMomentum()
	{
		return momentum;
	}

	@Override
	public boolean ifStop(NeuralNetwork ann, int iteration)
	{
		float score = evaluator.evaluate(ann, validateSet);
		if (score > bestScore)
		{
			bestScore = score;
			bestIter = iteration;
		}
		if (verbose)
		{
			out.format("iter:%d, score:%f\n", iteration, score);
		}
		if (iteration >= maxIteration)
		{
			if (verbose)
				out.format("best iter:%d, best score:%f\n", bestIter, bestScore);
			return true;
		}
		return false;
	}

	@Override
	public Float convertOutputForClassification(Float output)
	{
		if (outputConversionMap == null)
			return output;

		float best = output;
		float bestDiff = Float.POSITIVE_INFINITY;

		for (Float v : outputConversionMap.values())
		{
			if (Math.abs(v - output) < bestDiff)
			{
				best = v;
				bestDiff = v - output;
			}
		}

		return best;
	}
}
